package org.persistente;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.persistente.esquema.CampoAbstrato;
import org.persistente.esquema.CampoString;

/**
 * Rotinas de geração sqls para manipulação de dados.
 * @author philipe
 */
public abstract class GeradorSql {

    /**
     * Gera um sql para inserir um registro em banco de dados.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos que serão informados.
     * @return Sql de inserção.
     */
    public static String insere(String nomeTabela, Collection<CampoAbstrato> campos) {
        StringBuilder sql = new StringBuilder();

        sql.append("INSERT INTO ").append(nomeTabela).append('(');

        appendCampos(sql, campos).append(") VALUES(");

        appendInterrogacoes(sql, campos.size()).append(')');

        return sql.toString();
    }

    /**
     * Gera um sql para inserir um registro em banco de dados, retornando os valores gerados pelo banco de dados.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos que serão informados.
     * @param retorno Campos a serem retornados.
     * @return Sql de inserção.
     */
    public static String insereRetornando(String nomeTabela, Collection<CampoAbstrato> campos, Collection<CampoAbstrato> retorno) {
        StringBuilder sql = new StringBuilder();

        sql.append("INSERT INTO ").append(nomeTabela).append('(');

        appendCampos(sql, campos).append(") VALUES(");

        appendInterrogacoes(sql, campos.size()).append(')');

        if (retorno.size() > 0) {
            sql.append(" RETURNING ");
            appendCampos(sql, retorno);
        }

        return sql.toString();
    }

    /**
     * Gera um sql para alterar um registro em banco de dados.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos a serem alterados.
     * @param filtro Campos a serem utilizados como filtro.
     * @return Sql para alteração.
     */
    public static String atualiza(String nomeTabela, Collection<CampoAbstrato> campos, Collection<CampoAbstrato> filtro) {
        StringBuilder sql = new StringBuilder();

        sql.append("UPDATE ").append(nomeTabela);

        appendAlteracao(sql, campos);
        appendFiltro(sql, filtro);

        return sql.toString();
    }

    /**
     * Gera um sql para excluir um registro em banco de dados.
     * @param nomeTabela Nome da tabela.
     * @param filtro Campos para filtro.
     * @return Sql para exclusão.
     */
    public static String exclui(String nomeTabela, Collection<CampoAbstrato> filtro) {
        StringBuilder sql = new StringBuilder();

        sql.append("DELETE FROM ").append(nomeTabela);

        appendFiltro(sql, filtro);

        return sql.toString();
    }

    /**
     * Gera um sql para presquisar registros de banco de dados.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos a serem consultados.
     * @param filtro Filtro sql.
     * @return Sql de consulta.
     */
    public static String seleciona(String nomeTabela, Collection<CampoAbstrato> campos, String filtro) {

        StringBuilder sql = new StringBuilder();

        sql.append("SELECT ");
        appendCampos(sql, campos);
        sql.append(" FROM ").append(nomeTabela).append(' ').append(filtro);

        return sql.toString();
    }

    /**
     * Gera um sql para presquisar registros de banco de dados.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos a serem consultados.
     * @param filtro Filtro sql.
     * @param exata Se a pesquisa é exata ou não. Não-exata utiliza LIKE para strings.
     * @return Sql de consulta.
     */
    public static String seleciona(String nomeTabela, String campos, Collection<CampoAbstrato> filtro, boolean exata) {
        StringBuilder sql = new StringBuilder();

        sql.append("SELECT ").append(campos).append(" FROM ").append(nomeTabela);

        if (exata) {
            appendFiltro(sql, filtro);
        } else {
            appendFiltroNaoExato(sql, filtro);
        }

        return sql.toString();
    }

    /**
     * Gera uma pesquisa sql utilizando um comparador especificado.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos a serem retornados.
     * @param filtro Campos para filtro.
     * @param comparador Comparador a ser usado nos filtros.
     * @return Sql de pesquisa.
     */
    public static String seleciona(String nomeTabela, Collection<CampoAbstrato> campos, Collection<CampoAbstrato> filtro, boolean exata) {
        StringBuilder sql = new StringBuilder();

        sql.append("SELECT ");
        appendCampos(sql, campos);
        sql.append(" FROM ").append(nomeTabela);


        if (exata) {
            appendFiltro(sql, filtro);
        } else {
            appendFiltroNaoExato(sql, filtro);
        }

        return sql.toString();
    }

    /**
     * Gera uma pesquisa sql utilizando um comparador especificado.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos a serem retornados.
     * @param filtro Campos para filtro.
     * @param comparador Comparador a ser usado nos filtros.
     * @return Sql de pesquisa.
     */
    public static String seleciona(String nomeTabela, Collection<CampoAbstrato> campos, Collection<CampoAbstrato> filtro, String comparador) {
        StringBuilder sql = new StringBuilder();

        sql.append("SELECT ");
        appendCampos(sql, campos);
        sql.append(" FROM ").append(nomeTabela);
        appendFiltro(sql, filtro, comparador);

        return sql.toString();
    }

    /**
     * Gera um sql de pesquisa.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos a serem retornados.
     * @param filtro Campos para filtro.
     * @return Sql de pesquisa.
     */
    public static String pesquisa(String nomeTabela, Collection<CampoAbstrato> campos, Collection<CampoAbstrato> filtro) {
        return pesquisa(nomeTabela, campos, filtro, true, new ArrayList<CampoAbstrato>(), true, "");
    }

    /**
     * Gera um sql de pesquisa em banco de dados.
     * @param nomeTabela Nome da tabela.
     * @param campos Campos a serem retornados.
     * @param filtro Campos para filtro.
     * @param exata Se a pesquisa é exata. Não-exata utiliza LIKE para strings.
     * @param ordem Lista de campos para ordenar o resultado.
     * @param crescente Se a ordenação é crescente ou não (decrescente).
     * @param extra Sql extra para ser adicionado ao sql.
     * @return Sql de pesquisa gerado.
     */
    public static String pesquisa(String nomeTabela, Collection<CampoAbstrato> campos, Collection<CampoAbstrato> filtro, boolean exata, Collection<CampoAbstrato> ordem, boolean crescente, String extra) {
        StringBuilder sql = new StringBuilder();

        sql.append("SELECT ");
        appendCampos(sql, campos);
        sql.append(" FROM ").append(nomeTabela);

        if (exata) {
            appendFiltro(sql, filtro);
        } else {
            appendFiltroNaoExato(sql, filtro);
        }

        appendOrdem(sql, ordem, crescente);
        sql.append(extra);

        return sql.toString();
    }

    /**
     * Adiciona uma lista variáveis (?) separadas por vigulas ao sql.
     * @param sql StringBuilder a ser alterado.
     * @param qtd Quantidade de variáveis.
     * @return return O próprio StringBuilder após alterado.
     */
    private static StringBuilder appendInterrogacoes(StringBuilder sql, int qtd) {
        if (qtd > 0) {
            sql.append('?');
            for (int i = 1; i < qtd; ++i) {
                sql.append(',').append('?');
            }
        }
        return sql;
    }

    /**
     * Adiciona uma lista de nomes de campos separados por vírgula.
     * @param sql StringBuilder a ser alterado.
     * @param campos Lista de campos a serem adicionados.
     * @return O StringBuilder após alterado.
     */
    private static StringBuilder appendCampos(StringBuilder sql, Collection<CampoAbstrato> campos) {
        if (campos.size() > 0) {
            Iterator<CampoAbstrato> it = campos.iterator();

            sql.append(it.next().getNome());

            while (it.hasNext()) {
                sql.append(',');
                sql.append(it.next().getNome());
            }
        }
        return sql;
    }

    /**
     * Adiciona um filtro ao sql.
     * @param sql StringBuilder a ser alterado.
     * @param campos Campos a serem utilizados no filtro.
     * @return StringBuilder após alterado.
     */
    private static StringBuilder appendFiltro(StringBuilder sql, Collection<CampoAbstrato> campos) {
        return appendFiltro(sql, campos, "=");
    }

    /**
     * Adiciona um filtro sql com um comparador especificado.
     * @param sql StringBuilder a ser alterado.
     * @param campos Campos a serem  utilziados no filtro.
     * @param comparador Comparador a ser usado nos filtros.
     * @return StringBuilder após alterado.
     */
    private static StringBuilder appendFiltro(StringBuilder sql, Collection<CampoAbstrato> campos, String comparador) {
        if (campos.size() > 0) {
            Iterator<CampoAbstrato> it = campos.iterator();

            sql.append(" WHERE ");

            sql.append(it.next().getNome()).append(comparador).append('?');

            while (it.hasNext()) {
                sql.append(" AND ");
                sql.append(it.next().getNome()).append(comparador).append('?');
            }
        }
        return sql;
    }

    /**
     * Adiciona um filtro não-exato ao sql. Filtro não-exato utiliza LIKE para pesquisar strings.
     * @param sql StringBuilder a ser alterado.
     * @param campos Campos a serem utilizados no filtro.
     * @return StringBuilder após alterado.
     */
    private static StringBuilder appendFiltroNaoExato(StringBuilder sql, Collection<CampoAbstrato> campos) {
        if (campos.size() > 0) {
            Iterator<CampoAbstrato> it = campos.iterator();

            sql.append(" WHERE ");

            CampoAbstrato ac;

            ac = it.next();
            if (ac instanceof CampoString) {
                sql.append("UPPER(sem_acentos(");
                sql.append(ac.getNome());
                sql.append("))");
                sql.append(" LIKE UPPER(sem_acentos(?))");
            } else {
                sql.append(ac.getNome());
                sql.append("=?");
            }


            while (it.hasNext()) {
                ac = it.next();
                sql.append(" AND ");

                if (ac instanceof CampoString) {
                    sql.append("UPPER(sem_acentos(");
                    sql.append(ac.getNome());
                    sql.append("))");
                    sql.append(" LIKE UPPER(sem_acentos(?))");
                } else {
                    sql.append(ac.getNome());
                    sql.append("=?");
                }
            }
        }
        return sql;
    }

    /**
     * Adiciona uma lista de alterações (SET) para um sql UPDATE.
     * @param sql StringBuilder a ser alterado.
     * @param campos Campos a serem alterados.
     * @return StringBuilder após alterado.
     */
    private static StringBuilder appendAlteracao(StringBuilder sql, Collection<CampoAbstrato> campos) {
        if (campos.size() > 0) {
            Iterator<CampoAbstrato> it = campos.iterator();

            sql.append(" SET ");

            sql.append(it.next().getNome()).append("=?");

            while (it.hasNext()) {
                sql.append(',');
                sql.append(it.next().getNome()).append("=?");
            }
        }
        return sql;
    }

    /**
     * Adiciona uma lista de indicadores de ordenação.
     * @param sql StringBuilder a ser alterado.
     * @param campos Campos a serem utilizados na ordenação.
     * @param crescente Se a ordenação será crescente.
     * @return Sql após alterado.
     */
    private static StringBuilder appendOrdem(StringBuilder sql, Collection<CampoAbstrato> campos, boolean crescente) {
        if (campos.size() > 0) {
            Iterator<CampoAbstrato> it = campos.iterator();

            sql.append(" ORDER BY ").append(it.next().getNome());

            if (crescente) {
                while (it.hasNext()) {
                    sql.append(',').append(it.next().getNome());
                }
            } else {
                sql.append(" DESC ");
                while (it.hasNext()) {
                    sql.append(',').append(it.next().getNome()).append(" DESC ");
                }
            }

        }
        return sql;
    }
}
